COMP3141 Software System Design and Implementation

COMP3141: Software System Design and Implementation

Term 2, 2023

Quiz (Week 8)

Table of Contents

1 IO Monad

1.1 Question 1

What does the type IO String signify?

  1. A procedure that, when carried out, may involve input and output, before eventually returning a String.
  2. A function from the abstract state of the RealWorld to a pair of the RealWorld state and an String.
  3. An effectful program that, when executed, produces an String.
  4. All of the above views are valid interpretations

GHC internally models IO a similarly to the following type:

IO a =~ RealWorld -> (RealWorld, a)

This is a common view of IO and option 2. Perhaps an even more common view of IO is that it denotes procedure. For example,

getChar :: IO Char

could be viewed as a type representing a procedure that will produce a Char when it's carried out. This is a very intuitive way of thinking about IO: a function of type String -> IO (), such as putStrLn, takes a String and returns a procedure that, when carried out, results in the given string being printed. Keep in mind that the procedure is not carried out inside the Haskell program: rather, it's carried out when the program is executed.

1.2 Question 2

Imagine we had the following IO based API for manipulating a robot:

data Direction = L | R
forward    :: IO ()
obstructed :: IO Bool
turn       :: Direction -> IO ()

We wish to write a program that will move forward unless obstructed, in which case the robot should turn towards the L direction.

Which of the following is a type-correct implementation of the above procedure?

  1. robot = do
      sensed <- obstructed
      if sensed 
        then turn L
        else forward
      robot
    
  2. robot = do
      if obstructed
        then turn L
        else forward
      robot
    
  3. robot = do
      let sensed = obstructed
      if sensed
        then turn L
        else forward
      robot
    
  4. robot = do
      sensed <- obstructed
      if sensed
        then turn L
             robot
        else forward
             robot
    

Option 1 is correct. Option 2 uses an IO Bool (obstructed) where a Bool is required (in the if). Option 3 uses let to bind sensed to obstructed. That is, sensed now has type IO Bool, which once again is incorrectly used within the if. Option 4 places the robot looping call at the same indentation as turn L and forward, but without the do keyword they do not form a block and so Haskell would parse this as turn L robot which is not well-typed.

1.3 Question 3

Check all of the following programs that are equivalent to the IO action b:

b = do x <- getLine
       putStrLn (filter (not . isDigit) x)
       b
  1. a = getLine >>= putStrLn . filter (not . isDigit) >> a
  2. a = getLine >>= \x -> putStrLn (filter (not . isDigit) x) >>= \_ -> a
  3. a = (getLine >>= \x -> putStrLn (filter (not . isDigit) x)) >>= a
  4. a = do getLine >>= \x -> putStrLn . filter (not . isDigit); a
  5. a = do x <- getLine; putStrLn . filter (not . isDigit); a
  6. a = do x <- getLine; putStrLn . filter (not . isDigit) $ x; a
  7. a = do getLine >>= \x -> putStrLn (filter (not . isDigit) x); a

Options 3,4, and 5 don't type-check. The others are all equivalent.

1.4 Question 4

Which of the following defines a function that takes a String as its argument and causes it to be written to the standard output, given that putChar :: Char -> IO () takes a character as its parameter and writes it to the standard output.

  1. f [] = return ""
    f (x:xs) = putChar x >>= \_ -> xs
    
  2. f [] = return ()
    f (x:xs) = putChar x >>= \_ -> f xs
    
  3. f [] = return ()
    f (x:xs) = putChar x >>= \xs -> f (show xs)
    
  4. f xs = case xs of
      [] -> return ()
      (x:xs) -> f xs >>= \_ -> putChar x
    

The first and fourth are not even type-correct. The third is, but it's not terminating. The second implementation is correct.

1.5 Question 5

Pick all possible implementations that define a function mapIO :: (a -> b) -> IO a -> IO b that takes a function of type a -> b and "maps" it over a monadic value of type IO a to produce a value of type IO b?

  1. mapIO f m = return (f m)
    
  2. mapIO f m = m >>= \x -> return (f x)
    
  3. mapIO f m = m >>= \x -> f x
    
  4. mapIO f = (f <$>)
    

The first and third answers do not type-check. In the second implementation, we have that m has type IO a, so the variable x has type a. Therefore, the variable f x has type b, and return (f x) has type IO b as desired. The fourth answer works by using the fact that all Monad~s are ~Applicative.

1.6 Question 6

Which of the following types can a total (that is, non-partial) terminating function never have?

  1. a :: IO (a -> b) -> IO a -> IO b
  2. b :: IO (a -> b) -> a -> IO b
  3. c :: IO (a -> b) -> (a -> b)
  4. d :: a -> IO a

The first function, a is just a = fmap, using the fact that every Monad, including IO, is a Functor. Similarly, the third function, d is just d = return.

The second function can be implemented as follows:

b :: IO (a -> b) -> a -> IO b
b f x = f >>= \f' -> return (f' x)

This leaves the third function, which cannot be implemented: if we had such an implementation c, we could use that c to also implement the following function:

evalIO :: IO a -> a
evalIO p = (p >>= \x -> c (return (const x))) ()

but we know from the lecture that evaluating IO like this is impossible.

2 Other monads

2.1 Question 7

Which of the following expressions evaluates to the sum of the first 100 square numbers?

  1. foldl (++) [] [[x * x] | x <- [1..100]]
  2. sum [x^2 | x <- [1..100]]
  3. sum [const x x | x <- [1..100]]
  4. sum (map (^2) [1..100])

The first expression does not even type-check. The second is correct, as is the fourth. The third one calculates the sum of the first 100 integers, since const x x == x.

2.2 Question 8

Suppose we have a mysteryFunction :: Either a b -> a. Based on its type signature alone, what can we infer about the behaviour of the function?

  1. It cannot perform any IO.
  2. It is the identity function.
  3. It is partial.
  4. Its argument will always be of the form Left a

We know it its return type is not of the form IO a. It cannot be the identity function, because the identity function has a different type. We have no way of preventing the caller from giving the function either a Left or Right value at their leisure.

There can be no total implementation of this function, because for a mysteryFunction(Right x) there will be no way for the function to produce a value of type a.

Submission is already closed for this quiz. You can click here to check your submission (if any).

2023-08-13 Sun 12:52

Announcements RSS